package pl.umk.mat.fred.unicore.urc.storagemanagement.actions;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.util.URIUtil;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.unigrids.services.atomic.types.GridFileType;
import org.unigrids.services.atomic.types.ProtocolType;

import pl.umk.mat.fred.unicore.urc.storagemanagement.tools.StorageTools;
import pl.umk.mat.fred.unicore.urc.storagemanagement.tree.nodes.CatalogNode;
import pl.umk.mat.fred.unicore.urc.storagemanagement.tree.nodes.FileNode;
import pl.umk.mat.fred.unicore.urc.storagemanagement.tree.nodes.MultiplyCatalogNode;
import pl.umk.mat.fred.unicore.urc.storagemanagement.tree.utils.TreeUtils;
import pl.umk.mat.fred.unicore.urc.storagemanagement.unicore.Storage;
import pl.umk.mat.fred.unicore.urc.storagemanagement.views.StorageBrowserView;
import de.fzj.unicore.uas.client.FileTransferClient;
import de.fzj.unicore.wsrflite.xmlbeans.BaseFault;

public class DownloadRemoteFilesAction extends Action {

	private StorageBrowserView view;
	private Shell shell;
	
	public DownloadRemoteFilesAction(StorageBrowserView view) {
		this.view = view;
		this.setText("Download files");
	}
	
	public void run() {
		this.shell = PlatformUI.getWorkbench().getDisplay().getActiveShell();
		DirectoryDialog dialog = new DirectoryDialog(shell);
		final IStructuredSelection selection = (IStructuredSelection) view.getTreeViewer().getSelection();
		final String destinationDirectory = dialog.open();
		Job job = new Job("Downloading files") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				Date start = new Date();
				if (destinationDirectory != null) {
					if (selection.size() == 0) {
						monitor.done();
						return Status.OK_STATUS;
					}
					int size = TreeUtils.getSelectedFilesSize(selection);
					monitor.beginTask("Downloading files", size);
					for (Object obj : selection.toArray()) {
						if (obj instanceof FileNode) {
							FileNode node = (FileNode) obj;
							try {
								IStatus status = downloadFile(node.getStorage(), node.getPath(), node.getFileSize(), destinationDirectory, monitor);
								if (Status.CANCEL_STATUS.equals(status)) {
									return Status.CANCEL_STATUS;
								}
							} catch (Exception e) {
								e.printStackTrace();
							}
						} else if (obj instanceof CatalogNode) {
							CatalogNode node = (CatalogNode) obj;
							try {
								IStatus status = downloadDirectory(node.getStorage(), node.getPath(), destinationDirectory, monitor);
								if (Status.CANCEL_STATUS.equals(status)) {
									return Status.CANCEL_STATUS;
								}
							} catch (Exception e) {
								e.printStackTrace();
							}
						} else if (obj instanceof MultiplyCatalogNode) {
							IStatus status = downloadMultiplyDirectory((MultiplyCatalogNode) obj, destinationDirectory, monitor);
							if (Status.CANCEL_STATUS.equals(status)) {
								return Status.CANCEL_STATUS;
							}
						}
						if (monitor.isCanceled()) {
							return Status.CANCEL_STATUS;
						}
					}
				}
				monitor.done();
				Date end = new Date();
				long tmp = end.getTime() - start.getTime();
				System.out.println("time " + tmp/60000 + ":" + (tmp%60000)/1000);
				return org.eclipse.core.runtime.Status.OK_STATUS;
			}

		};
		job.schedule();
	}
	
	public IStatus downloadDirectory(Storage storage, String remotePath, String localDirectory, IProgressMonitor monitor) throws Exception {
		monitor.subTask(remotePath);
		String name = TreeUtils.getNameFromPath(remotePath);
		File directory = new File(localDirectory + System.getProperty("file.separator") + name);
		if (!directory.exists()) {
			directory.mkdirs();
		}
		GridFileType[] files = null;
		files = storage.listDirectory(URIUtil.encode(remotePath, org.apache.commons.httpclient.URI.allowed_fragment));
		
		for (GridFileType gft : files) {
			if (monitor.isCanceled()) {
				return Status.CANCEL_STATUS;
			}
			if (gft.getIsDirectory()) {
				downloadDirectory(storage, URIUtil.decode(gft.getPath()), directory.getAbsolutePath(), monitor);
			} else {
				downloadFile(storage, URIUtil.decode(gft.getPath()), gft.getSize(), directory.getAbsolutePath(), monitor);
			}
		}
		return Status.OK_STATUS;
	}
	
	private IStatus downloadMultiplyDirectory(MultiplyCatalogNode mc,
			String localDirectory, IProgressMonitor monitor) {
		if (mc.getCatalogs().isEmpty()) {
			throw new IllegalStateException("Multiply Catalog is empty");
		}
		
		String name = TreeUtils.getNameFromPath(mc.getCatalogs().get(0).getPath());
		File directory = new File(localDirectory + System.getProperty("file.separator") + name);
		if (!directory.exists()) {
			directory.mkdirs();
		}
		
		for (CatalogNode cn : mc.getCatalogs()) {
			GridFileType[] files = null;
			Storage storage = cn.getStorage();
			try {
				files = storage.listDirectory(
						URIUtil.encode(cn.getPath(), org.apache.commons.httpclient.URI.allowed_fragment));
			} catch (URIException e) {
				continue;
			} catch (BaseFault e) {
				continue;
			}
			
			for (GridFileType gft : files) {
				if (monitor.isCanceled()) {
					return Status.CANCEL_STATUS;
				}
				if (gft.getIsDirectory()) {
					try {
						downloadDirectory(storage, URIUtil.decode(gft.getPath()), directory.getAbsolutePath(), monitor);
					} catch (Exception e) {
						continue;
					}
				} else {
					try {
						downloadFile(storage, URIUtil.decode(gft.getPath()), gft.getSize(), directory.getAbsolutePath(), monitor);
					} catch (Exception e) {
						continue;
					}
				}
			}
		}
		return Status.OK_STATUS;
	}
	
	public IStatus downloadFile(Storage storage, String remotePath, long remoteFileSize, String localDirectory, IProgressMonitor monitor) throws Exception {
		monitor.subTask(remotePath);
		FileTransferClient ftClient = null;
		BufferedOutputStream os = null;
		try {
			ProtocolType.Enum protocol = StorageTools.getProtocol(remoteFileSize, storage.getSupportedProtocols());
			String encodedPath = URIUtil.encode(remotePath, org.apache.commons.httpclient.URI.allowed_fragment);
			Map<String,String> extraParameters = new HashMap<String, String>();
			StorageTools.provideParameters(extraParameters, protocol.toString());
			System.out.println("Fred download " + protocol);
			
			ftClient = storage.getExport(encodedPath, protocol, extraParameters);
			String name = TreeUtils.getNameFromPath(remotePath);
			File directory = new File(localDirectory);
			if (!directory.exists()) {
				directory.mkdirs();
			}
			File file = new File(localDirectory + System.getProperty("file.separator") + name);
			if (file.exists()) {
				File oldFile = file;
				int i = 1;
				do {	
				file = new File(oldFile.getAbsolutePath() + "(" + (i++) + ")");
				} while(file.exists());
			}
			os = new BufferedOutputStream(new FileOutputStream(file));
			if (monitor.isCanceled()) {
				return Status.CANCEL_STATUS;
			}
			try {
			ftClient.readAllData(os);
			} catch (Exception e) {
				if (ProtocolType.UFTP.equals(protocol)) {
					final MessageDialog dialog = new MessageDialog(shell, "Error", null, 
							"Probably UFTP protocol configuration is incorrect. The file will be downloaded using the BFT protocol.", SWT.ERROR, new String[]{"OK"}, 0);
					shell.getDisplay().asyncExec(new Runnable() {
						@Override
						public void run() {
							dialog.open();
						}
					});
					if (ftClient != null) {
						ftClient.destroy();
					}
					ftClient = storage.getExport(encodedPath, ProtocolType.BFT, new HashMap<String, String>());
					ftClient.readAllData(os);
				}
			}
			monitor.worked((int)ftClient.getSourceFileSize());
		} finally {
			if (ftClient != null) {
				try {
					ftClient.destroy();
				} catch (Exception e) {
				}
			}
			if (os != null) {
				os.close();
			}
		}
		return Status.OK_STATUS;
	}
}
